/*---------------------------------------
/ Test program for System Calls
/ Added in Project2
/
/ This Test is to test the syscalls:
/	Fork, Exit, Exec
/
/ by Nick
/ modified by OceanMaster
/---------------------------------------*/

#include "syscall.h"

int ID = 0;
int Lock;
int CV;
int pid;
int accum = 0;

void thread1()
{
	int i;
	int id;

	AcquireLock(Lock);
	id = ID ++;
	ReleaseLock(Lock);

	
	for(i=0;i<5;i++)						/*Thread 1 do something*/
	{
		WriteFormat("Thread %d ! \n",14,&id,1);
		Yield();
	}

	AcquireLock(Lock);						/*Mark self quited*/
	accum ++;
	ReleaseLock(Lock);

	Exit(0);
}

void thread2()
{
	int i;
	int id;

	AcquireLock(Lock);
	id = ID ++;
	ReleaseLock(Lock);

	
	for(i=0;i<5;i++)						/*Thread 2 do something*/
	{
		WriteFormat("Thread %d ! \n",14,&id,1);
		Yield();
	}
	
	while(1)								/*Wait for thread 1 to Stop*/
	{
		AcquireLock(Lock);
		if(accum == 1)
		{
			accum = 0;
			SignalCondition(CV,Lock);		/*Wake up main*/
			ReleaseLock(Lock);
			break;
		}
		ReleaseLock(Lock);
		Yield();
	}

	Exit(0);
}

/* Bullet test for different types of parameters */
void test1_main()
{
	WriteFormat("\n========= Test Case 0 =========  \n",37,0,0);
	WriteFormat("/*Bullet-Proof Test for Exit,    \n",35,0,0);
	WriteFormat("  Fork, and Exec             */  \n",35,0,0);
	

	WriteFormat("Executing Statement: Exit(-1)    \n",35,0,0);
	Exit(-1);
	WriteFormat("Executing Should FAIL because -1 is an invalid status   \n\n",60,0,0);
	
	WriteFormat("Executing Statement: Fork(-2)    \n",35,0,0);
	Fork(-2);
	WriteFormat("Executing Should FAIL because -2 is an invalid address  \n\n",60,0,0);
	
	WriteFormat("Executing Statement: Fork(20000) \n",35,0,0);
	Fork(20000);
	WriteFormat("Executing Should FAIL because 20000 exceeds code segment\n\n",60,0,0);
	
	WriteFormat("Executing Statement: Exec('hello',5)  \n",40,0,0);
	Exec("hello",5);
	WriteFormat("Executing Should FAIL because 'hello' doesn't exit.     \n\n",60,0,0);
	
	WriteFormat("Executing Statement: Exec(-1,5)  \n",35,0,0);
	Exec(-1,5);
	WriteFormat("Executing Should FAIL because -1 is an invalid address  \n\n",60,0,0);
	
	WriteFormat("Executing Statement: Exec('../test/testPart2_1', 18)      \n",60,0,0);
	Exec("../test/testPart2_1", 18);
	WriteFormat("Executing Should FAIL because name length is wrong      \n\n",60,0,0);
	
	WriteFormat("Executing Statement: Exec('../test/testPart2_1', 50000)   \n",60,0,0);
	Exec("../test/testPart2_1", 50000);
	WriteFormat("Executing Should FAIL because name length beyond bounds   \n",60,0,0);

	WriteFormat("========== Test End ==========   \n",35,0,0);
}

void test2_main()
{
	int i;
	int sum=0;
	Lock = CreateLock("ControlLock",11);
	CV = CreateCondition("ControlCondition",16);	
	AcquireLock(Lock);
	
/*
	Test 1:
	Fork two threads Simultaneously
	Expectation:	Stack allocation is correct (one-to-one mapping between virtual memory and physical memory)
					Two threads cannot share the stack
					The stack size will double when space is not enough
					Yield works well
*/ 
	WriteFormat("\n========= Test Case 1 =========\n",35,0,0);
	WriteFormat("/*Test memory allocation for multi thread, \n",45,0,0);
	WriteFormat("  One-to-one mapping from physical to virtual pages  \n",55,0,0);
	WriteFormat("  Two threads dont share one memory page   \n",45,0,0);
	WriteFormat("  Stack size will double when not enough*/\n\n",46,0,0);
	
	WriteFormat("Main thread Fork 2 new thread    \n",35,0,0);
	Fork(thread1);
	Fork(thread2);	
	WriteFormat("Main thread wait for them to finish        \n",45,0,0);
	WaitCondition(CV,Lock);
	WriteFormat("========== Test End ==========   \n",35,0,0);

/*
	Test 2: 
	Fork two threads after Test 1
	Expectation:  Physical pages that are recycled by thread 1 or 2 can be reused
				  Stack allocation is not contiguous in physical memory, but in virtual memory,
				  these new stacks are appended at the tail of the old stacks
*/ 
	WriteFormat("\n========= Test Case 2 =========\n",35,0,0);
	WriteFormat("/*Test memory allocation for multi thread, \n",45,0,0);
	WriteFormat("  Physical pages recycled can be reused;   \n",45,0,0);
	WriteFormat("  Stack is contiguous in virtual space, but\n",45,0,0);
	WriteFormat("  not necessarily contiguous in physical;  \n",45,0,0);
	WriteFormat("  New stack follow old one in VirtualSpace*/\n\n",48,0,0);
	
	WriteFormat("Main thread Fork 2 new thread    \n",35,0,0);
	Fork(thread1);
	Fork(thread2);
	WriteFormat("Main thread wait for them to finish        \n",45,0,0);
	WaitCondition(CV,Lock);
	WriteFormat("========== Test End ==========   \n",35,0,0);

/*
	Test 3:
	Execute two user programs
	Expectation: Each user program will be allocated two stacks whenever it is first loaded
				 The address space for the first process is not contiguous in physical memory, but the second one is contiguous 
				 These two address spaces cannot share memory
*/
	WriteFormat("\n========= Test Case 3 =========\n",35,0,0);
	WriteFormat("/*Test memory allocation for multi process,\n",45,0,0);
	WriteFormat("  Processes never share physical memory page*/\n\n",50,0,0);
	
	WriteFormat("Main thread Exec '../test/testPart2_1'     \n",45,0,0);
	Exec("../test/testPart2_1",19);
	WriteFormat("Main thread wait for that proces to finish \n",45,0,0);
	Yield();
	sum=0;
	for(i=0;i<10000;i++)
	{
		sum += i;
	}
	WriteFormat("========== Test End ==========   \n",35,0,0);

/*
	Test 4:
	Exit: Not the last executing thread in a process
	Expectation: in testPart2_2, when main function exit, t1 thread should still be in the stack
 */
	WriteFormat("\n========= Test Case 4 =========\n",35,0,0);
	WriteFormat("/*Test Exit mechanism			  \n",35,0,0);
	WriteFormat(" In another Process, main thread \n",35,0,0);
	WriteFormat(" Exited before Forked thread */\n\n",35,0,0);
	
	WriteFormat("Main thread Exec '../test/testPart2_2'     \n",45,0,0);
	Exec("../test/testPart2_2",19);
	WriteFormat("Main thread wait for that proces to finish \n",45,0,0);
	sum=0;
	for(i=0;i<10000;i++)
	{
		sum += i;
		if(i%1000 == 0)
			Yield();
	}
	WriteFormat("testPart2_2 should exit normally and recycle memory after ALL threads end \n",76,0,0);
	WriteFormat("========== Test End ==========   \n",35,0,0);

/*
	Test 5:
	Exit: Last executing thread in a process(not the last process)
	Expectation: in testPart2_3, when main function exit, t1 should also be stopped
*/
	WriteFormat("\n========= Test Case 5 =========\n",35,0,0);
	WriteFormat("/*Test Exit mechanism			  \n",35,0,0);
	WriteFormat(" In another Process, main thread \n",35,0,0);
	WriteFormat(" Exit as last ACTIVE thread in   \n",35,0,0);
	WriteFormat(" progress will stop progress   \n\n",35,0,0);
	

	WriteFormat("Main thread Exec '../test/testPart2_3'     \n",45,0,0);
	Exec("../test/testPart2_3",19);
	WriteFormat("Main thread wait for that proces to finish \n",45,0,0);
	sum=0;
	for(i=0;i<10000;i++)
	{
		sum += i;
		if(i%1000 == 0)
			Yield();
	}
	WriteFormat("testPart2_3 should exit normally and recycle memory after process end. The sleeping thread should be stoped either. \n",118,0,0);
	WriteFormat("========== Test End ==========   \n",35,0,0);

/*
	Test 6:
	Exit: Last thread in last process (halt condition)
	Expectation: this program should exit before testPart2_4 really start, 
				 When testPart2_4 end its for loop, it should also halt nachos
 */
	WriteFormat("\n========= Test Case 6 =========\n",35,0,0);
	WriteFormat("/*Test Exit mechanism			  \n",35,0,0);
	WriteFormat(" In another Process, main thread \n",35,0,0);
	WriteFormat(" Exit as last thread in last progress*/\n\n",43,0,0);
	
	WriteFormat("Main thread Delete Locks and Conditions  \n",43,0,0);
	ReleaseLock(Lock);
	DestroyLock(Lock);
	DestroyCondition(CV);
	WriteFormat("Main thread Exec '../test/testPart2_4'     \n",45,0,0);
	Exec("../test/testPart2_4",19);
	WriteFormat("Main thread Will Exit            \n",35,0,0);
	WriteFormat("========== Test End ==========   \n",35,0,0);

}

int main()
{	
	test1_main();
	test2_main();
	Exit(0);
}
